﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

namespace PLCWpfTest.Controls
{
    /// <summary>
    /// THDashboard.xaml 的交互逻辑
    /// </summary>
    public partial class DoubleDashboard : UserControl
    {
        private const double RADIUS = 95;

        private static readonly DependencyProperty OuterRingStrokeProperty = DependencyProperty.Register(nameof(OuterRingStroke), typeof(Brush), typeof(DoubleDashboard), new PropertyMetadata(new SolidColorBrush(Colors.Cyan), null, OuterRingStrokeCoerceValueCallback));

        private static readonly DependencyProperty WarningStrokeProperty = DependencyProperty.Register(nameof(WarningStroke), typeof(Brush), typeof(DoubleDashboard), new PropertyMetadata(new SolidColorBrush(Colors.White), null, WarningStrokeCoerceValueCallback));

        private static readonly DependencyProperty LegendTextForegroundProperty = DependencyProperty.Register(nameof(LegendTextForeground), typeof(Brush), typeof(DoubleDashboard), new PropertyMetadata(new SolidColorBrush(Colors.White), null, LegendTextForegroundCoerceValueCallback));

        private static readonly DependencyProperty OuterRingStrokeThicknessProperty = DependencyProperty.Register(nameof(OuterRingStrokeThickness), typeof(double), typeof(DoubleDashboard), new PropertyMetadata(3.0, null, OuterRingStrokeThicknessCoerceValueCallback));

        private static readonly DependencyProperty InnerRingStrokeThicknessProperty = DependencyProperty.Register(nameof(InnerRingStrokeThickness), typeof(double), typeof(DoubleDashboard), new PropertyMetadata(5.0, null, InnerRingStrokeThicknessCoerceValueCallback));

        private static readonly DependencyProperty OneInnerRingStrokeProperty = DependencyProperty.Register(nameof(OneInnerRingStroke), typeof(Brush), typeof(DoubleDashboard), new PropertyMetadata(new SolidColorBrush(Colors.Cyan), null, OneInnerRingStrokeCoerceValueCallback));

        private static readonly DependencyProperty OneInnerRingValueProperty = DependencyProperty.Register(nameof(OneInnerRingValue), typeof(double), typeof(DoubleDashboard), new PropertyMetadata(0.0, OneInnerRingValuePropertyChangedCallback, null));

        private static readonly DependencyProperty TwoInnerRingStrokeProperty = DependencyProperty.Register(nameof(TwoInnerRingStroke), typeof(Brush), typeof(DoubleDashboard), new PropertyMetadata(new SolidColorBrush(Colors.Cyan), null, TwoInnerRingStrokeCoerceValueCallback));

        private static readonly DependencyProperty TwoInnerRingValueProperty = DependencyProperty.Register(nameof(TwoInnerRingValue), typeof(double), typeof(DoubleDashboard), new PropertyMetadata(0.0, TwoInnerRingValuePropertyChangedCallback, null));

        [Category("自定义属性"), Description("最小限度")]
        public int MinLimit { get; set; } = 0;

        [Category("自定义属性"), Description("最大限度")]
        public int MaxLimit { get; set; } = 90;

        private int _warningAngle = 120;
        [Category("自定义属性"), Description("报警角度")]
        public int WarningAngle
        {
            get { return _warningAngle; }
            set
            {
                if (value < 0)
                {
                    value = 0;
                }
                else if (value > 180)
                {
                    value = 180;
                }
                _warningAngle = value;
            }
        }

        [Bindable(true), Category("自定义属性"), Description("外环安全画刷")]
        public Brush OuterRingStroke
        {
            get { return (Brush)GetValue(OuterRingStrokeProperty); }
            set { SetValue(OuterRingStrokeProperty, value); }
        }

        [Bindable(true), Category("自定义属性"), Description("报警画刷")]
        public Brush WarningStroke
        {
            get { return (Brush)GetValue(WarningStrokeProperty); }
            set { SetValue(WarningStrokeProperty, value); }
        }

        [Bindable(true), Category("自定义属性"), Description("刻度文本画刷")]
        public Brush LegendTextForeground
        {
            get { return (Brush)GetValue(LegendTextForegroundProperty); }
            set { SetValue(LegendTextForegroundProperty, value); }
        }

        private double _legendTextScale = 0.93;
        [Bindable(true), Category("自定义属性"), Description("刻度文本缩放比例")]
        public double LegendTextScale
        {
            get
            {
                return _legendTextScale;
            }
            set
            {
                if (value < 0)
                {
                    value = 0;
                }
                else if (value > 1)
                {
                    value = 1;
                }
                _legendTextScale = value;
            }
        }

        [Bindable(true), Category("自定义属性"), Description("外环厚度")]
        public double OuterRingStrokeThickness
        {
            get { return (double)GetValue(OuterRingStrokeThicknessProperty); }
            set { SetValue(OuterRingStrokeThicknessProperty, value); }
        }

        [Bindable(true), Category("自定义属性"), Description("内环厚度")]
        public double InnerRingStrokeThickness
        {
            get { return (double)GetValue(InnerRingStrokeThicknessProperty); }
            set { SetValue(InnerRingStrokeThicknessProperty, value); }
        }

        [Bindable(true), Category("自定义属性"), Description("第一个内环画刷")]
        public Brush OneInnerRingStroke
        {
            get { return (Brush)GetValue(OneInnerRingStrokeProperty); }
            set { SetValue(OneInnerRingStrokeProperty, value); }
        }

        [Bindable(true), Category("自定义属性"), Description("第一个内环值")]
        public double OneInnerRingValue
        {
            get { return (double)GetValue(OneInnerRingValueProperty); }
            set { SetValue(OneInnerRingValueProperty, value); }
        }

        private double _oneInnerRingScale = 0.55;
        [Bindable(true), Category("自定义属性"), Description("第一个内环缩放比例")]
        public double OneInnerRingScale
        {
            get
            {
                return _oneInnerRingScale;
            }
            set
            {
                if (value < 0)
                {
                    value = 0;
                }
                else if (value > 1)
                {
                    value = 1;
                }
                _oneInnerRingScale = value;
            }
        }

        [Bindable(true), Category("自定义属性"), Description("第二个内环画刷")]
        public Brush TwoInnerRingStroke
        {
            get { return (Brush)GetValue(TwoInnerRingStrokeProperty); }
            set { SetValue(TwoInnerRingStrokeProperty, value); }
        }

        [Bindable(true), Category("自定义属性"), Description("第二个内环值")]
        public double TwoInnerRingValue
        {
            get { return (double)GetValue(TwoInnerRingValueProperty); }
            set { SetValue(TwoInnerRingValueProperty, value); }
        }

        private double _twoInnerRingScale = 0.35;
        [Bindable(true), Category("自定义属性"), Description("第二个内环缩放比例")]
        public double TwoInnerRingScale
        {
            get
            {
                return _twoInnerRingScale;
            }
            set
            {
                if (value < 0)
                {
                    value = 0;
                }
                else if (value > 1)
                {
                    value = 1;
                }
                _twoInnerRingScale = value;
            }
        }

        public DoubleDashboard()
        {
            InitializeComponent();
            DrawOuterRing();
        }

        private static object OuterRingStrokeCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            Brush outerRingStroke = (Brush)baseValue;
            doubleDashboard.SecurityPath.Stroke = outerRingStroke;
            return outerRingStroke;
        }

        private static object WarningStrokeCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            Brush warningStroke = (Brush)baseValue;
            doubleDashboard.WarningPath.Stroke = warningStroke;
            doubleDashboard.OneWarningPath.Stroke = warningStroke;
            doubleDashboard.TwoWarningPath.Stroke = warningStroke;
            return warningStroke;
        }

        private static object LegendTextForegroundCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            Brush legendTextForeground = (Brush)baseValue;
            foreach (TextBlock textBlock in doubleDashboard.DashboardCanvas.Children.OfType<TextBlock>())
            {
                textBlock.Foreground = legendTextForeground;
            }
            return legendTextForeground;
        }

        private static object OuterRingStrokeThicknessCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            double outerRingStrokeThickness = (double)baseValue;
            doubleDashboard.SecurityPath.StrokeThickness = outerRingStrokeThickness;
            doubleDashboard.WarningPath.StrokeThickness = outerRingStrokeThickness;
            return outerRingStrokeThickness;
        }

        private static object InnerRingStrokeThicknessCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            double innerRingStrokeThickness = (double)baseValue;
            doubleDashboard.OneSecurityPath.StrokeThickness = innerRingStrokeThickness;
            doubleDashboard.OneWarningPath.StrokeThickness = innerRingStrokeThickness;
            doubleDashboard.TwoSecurityPath.StrokeThickness = innerRingStrokeThickness;
            doubleDashboard.TwoWarningPath.StrokeThickness = innerRingStrokeThickness;
            return innerRingStrokeThickness;
        }

        private static object OneInnerRingStrokeCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            Brush oneInnerRingStroke = (Brush)baseValue;
            doubleDashboard.OneSecurityPath.Stroke = oneInnerRingStroke;
            return oneInnerRingStroke;
        }

        private static void OneInnerRingValuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
            {
                return;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            double oneInnerRingValue = (double)e.NewValue;

            doubleDashboard.DrawInnerRing(oneInnerRingValue, doubleDashboard.OneSecurityRange, doubleDashboard.OneWarningRange, RADIUS * doubleDashboard.OneInnerRingScale);
        }

        private static object TwoInnerRingStrokeCoerceValueCallback(DependencyObject d, object baseValue)
        {
            if (baseValue == null)
            {
                return baseValue;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            Brush twoInnerRingStroke = (Brush)baseValue;
            doubleDashboard.TwoSecurityPath.Stroke = twoInnerRingStroke;
            return twoInnerRingStroke;
        }

        private static void TwoInnerRingValuePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue == null)
            {
                return;
            }
            DoubleDashboard doubleDashboard = (DoubleDashboard)d;
            double twoInnerRingValue = (double)e.NewValue;
            doubleDashboard.DrawInnerRing(twoInnerRingValue, doubleDashboard.TwoSecurityRange, doubleDashboard.TwoWarningRange, RADIUS * doubleDashboard.TwoInnerRingScale);
        }

        /// <summary>
        /// 绘制内环
        /// </summary>
        /// <param name="value"></param>
        /// <param name="securityRange"></param>
        /// <param name="warningRange"></param>
        /// <param name="radius"></param>
        /// <exception cref="InvalidOperationException"></exception>
        private void DrawInnerRing(double value, PathFigure securityRange, PathFigure warningRange, double radius)
        {
            if (MinLimit >= MaxLimit)
            {
                throw new InvalidOperationException("The minimum limit cannot be greater than or equal to the maximum limit");
            }
            if (value < MinLimit)
            {
                value = MinLimit;
            }
            else if (value > MaxLimit)
            {
                value = MaxLimit;
            }
            int degree = ValueToDegree(value);
            bool isLarge = degree > WarningAngle;
            PathSegmentCollection securitySegments = new PathSegmentCollection();
            if (degree > 0)
            {
                for (int i = 0; i <= (isLarge ? WarningAngle : degree); i++)
                {
                    AddSegment(i, securitySegments, radius);
                }
                securitySegments.RemoveAt(securitySegments.Count - 1);
                securityRange.StartPoint = ((QuadraticBezierSegment)securitySegments[0]).Point1;
                securityRange.Segments = securitySegments;
            }
            else
            {
                securityRange.Segments = new PathSegmentCollection();
            }

            if (isLarge)
            {
                PathSegmentCollection warningSegments = new PathSegmentCollection();
                for (int i = WarningAngle; i <= degree; i++)
                {
                    AddSegment(i, warningSegments, radius);
                }
                warningSegments.RemoveAt(warningSegments.Count - 1);
                warningRange.StartPoint = ((QuadraticBezierSegment)warningSegments[0]).Point1;
                warningRange.Segments = warningSegments;
            }
            else
            {
                warningRange.Segments = new PathSegmentCollection();
            }
        }

        /// <summary>
        /// 值转换为角度
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private int ValueToDegree(double value)
        {
            int offset = 0 - MinLimit;
            double oneUnit = 180.0 / (MaxLimit - MinLimit);
            int degree = (int)Math.Round(oneUnit * (value + offset));
            return degree;
        }

        /// <summary>
        /// 画外环
        /// </summary>
        private void DrawOuterRing()
        {
            PathSegmentCollection securitySegments = new PathSegmentCollection();
            for (int i = 0; i <= WarningAngle; i++)
            {
                AddSegment(i, securitySegments, RADIUS);
                if (i % 30 == 0)
                {
                    AddOuterRingLegend(i, OuterRingStroke, RADIUS);
                    AddOuterRingLegendText(i);
                }
            }
            securitySegments.RemoveAt(securitySegments.Count - 1);
            SecurityRange.StartPoint = ((QuadraticBezierSegment)securitySegments[0]).Point1;
            SecurityRange.Segments = securitySegments;

            PathSegmentCollection warningSegments = new PathSegmentCollection();
            for (int i = WarningAngle; i <= 180; i++)
            {
                AddSegment(i, warningSegments, RADIUS);
                if (i % 30 == 0)
                {
                    AddOuterRingLegend(i, WarningStroke, RADIUS);
                    AddOuterRingLegendText(i);
                }
            }
            warningSegments.RemoveAt(warningSegments.Count - 1);
            WarningRange.StartPoint = ((QuadraticBezierSegment)warningSegments[0]).Point1;
            WarningRange.Segments = warningSegments;
        }

        /// <summary>
        /// 添加外环刻度
        /// </summary>
        /// <param name="degree"></param>
        /// <param name="brush"></param>
        /// <param name="radius"></param>
        private void AddOuterRingLegend(int degree, Brush brush, double radius)
        {
            RotateTransform rotateTransform = new RotateTransform();
            rotateTransform.Angle = -90 + degree;
            Line line = new Line();
            line.X1 = 0;
            line.Y1 = -radius - 3;
            line.X2 = 0;
            line.Y2 = -radius + 3;
            line.Stroke = brush;
            line.StrokeThickness = OuterRingStrokeThickness;
            line.RenderTransform = rotateTransform;
            DashboardCanvas.Children.Add(line);
        }

        /// <summary>
        /// 添加外环刻度文本
        /// </summary>
        /// <param name="degree"></param>
        private void AddOuterRingLegendText(int degree)
        {
            TextBlock textBlock = new TextBlock();
            textBlock.Foreground = LegendTextForeground;
            textBlock.Text = DegreeToLegend(degree).ToString();
            textBlock.FontSize = 8;
            Point point = GetPoint(degree, RADIUS * LegendTextScale);
            double x = point.X, y = point.Y;
            int len = textBlock.Text.Length;
            if (degree >= 90)
            {
                x -= degree > 90 ? len * 5 : len * 2.5;
            }
            Canvas.SetTop(textBlock, y);
            Canvas.SetLeft(textBlock, x);
            DashboardCanvas.Children.Add(textBlock);
        }

        /// <summary>
        /// 角度转换为值
        /// </summary>
        /// <param name="degree"></param>
        /// <returns></returns>
        private int DegreeToLegend(int degree)
        {
            int offset = 0 - MinLimit;
            double oneUnit = 180.0 / (MaxLimit - MinLimit);
            int legend = (int)Math.Round(degree / oneUnit) - offset;
            return legend;
        }

        /// <summary>
        /// 添加内外环段
        /// </summary>
        /// <param name="degree"></param>
        /// <param name="myPathSegmentCollection"></param>
        /// <param name="radius"></param>
        /// <returns></returns>
        public QuadraticBezierSegment AddSegment(int degree, PathSegmentCollection myPathSegmentCollection, double radius)
        {
            QuadraticBezierSegment segment = new QuadraticBezierSegment();
            segment.Point1 = GetPoint(degree, radius);
            segment.Point2 = GetPoint(degree + 1, radius);
            myPathSegmentCollection.Add(segment);
            return segment;
        }

        private Point GetPoint(int degree, double radius)
        {
            double oneRadian = Math.PI / 180;
            double radian = (-180 + degree) * oneRadian;
            double x = Math.Cos(radian) * radius;
            double y = Math.Sin(radian) * radius;
            return new Point(x, y);
        }
    }
}
